using BlazorReset.Models;
using Newtonsoft.Json.Linq;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Versioning;
using System.Text;

namespace BlazorReset.Utils;

public static class Helper
{

	[SupportedOSPlatform("windows")]
	[SupportedOSPlatform("linux")]
	[SupportedOSPlatform("macos")]
	public static Task<Device[]> SearchDevicesAsync(int timeoutSec, CancellationToken token)
	{
		var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
		cts.CancelAfter(timeoutSec * 1000);

		return SearchDevicesAsyncCore(cts.Token);
	}

	[SupportedOSPlatform("windows")]
	[SupportedOSPlatform("linux")]
	[SupportedOSPlatform("macos")]
	private async static Task<Device[]> SearchDevicesAsyncCore(CancellationToken token)
	{
		static Device Parse(byte[] buffer)
		{
			var device = new Device(Encoding.UTF8.GetString(buffer, 8, 20).TrimEnd('\0')
				, Encoding.UTF8.GetString(buffer, 28, 20).TrimEnd('\0')
				, Encoding.UTF8.GetString(buffer, 48, 20).TrimEnd('\0')
				//, Encoding.UTF8.GetString(buffer, 68, 16).TrimEnd('\0')
				, Encoding.UTF8.GetString(buffer, 84, 32).TrimEnd('\0')
				, Encoding.UTF8.GetString(buffer, 116, 32).TrimEnd('\0'));
			return device;
		}

		static async Task BroadcastQuery(CancellationToken token)
		{
			using var client = new UdpClient();
			client.JoinMulticastGroup(IPAddress.Parse("224.0.1.1"));

			var multicast = new IPEndPoint(IPAddress.Parse("224.0.1.1"), 6100);
			Byte[] query = [0xbb, 0x0b, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xbb, 0x0b, 0x00, 0x00];
			while (true)
			{
				token.ThrowIfCancellationRequested();
				await client.SendAsync(query, multicast, token);
				await Task.Delay(2000, token);
			}
		}

		static async Task ReceiveResponse(IPAddress ip, ConcurrentDictionary<Device, bool> devices, CancellationToken token)
		{
			//Configuration
			var interfaceEndPoint = new IPEndPoint(ip, 6100);
			var multicastIp = IPAddress.Parse("224.0.1.1");
			var multicastEndPoint = new IPEndPoint(multicastIp, 6100);
			using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
			socket.ExclusiveAddressUse = false;
			socket.MulticastLoopback = false;
			socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
			MulticastOption option = new MulticastOption(multicastEndPoint.Address, ip);
			socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, option);

			//bind on a network interface
			socket.Bind(interfaceEndPoint);

			byte[] buffer = new byte[1500];

			DateTime startTime = DateTime.Now;
			int len = 0;

			while (true)
			{
				token.ThrowIfCancellationRequested();
				try
				{
					len = await socket.ReceiveAsync(buffer, token);
					if (len == 0) break;
				}
				catch (OperationCanceledException)
				{
					break;
				}

				int type = BitConverter.ToInt32(buffer, 0);
				if (type == 3004)
				{
					if (len > 120)
					{
						Device device = Parse(buffer);
						if (device.System is not ("Depi" or "Child" or "DuDu"))
						{
							devices.TryAdd(device, true);
						}

					}
				}
			}

		}


		var devices = new ConcurrentDictionary<Device, bool>();
		string name = Dns.GetHostName();
		var ips = await Dns.GetHostAddressesAsync(name);
		ips = ips.Where(ipa => ipa.AddressFamily == AddressFamily.InterNetwork).ToArray();
		try
		{
			await Parallel.ForEachAsync(ips, token, async (ip, token) =>
			{
				_ = BroadcastQuery(token);

				await ReceiveResponse(ip, devices, token);

			});
		}
		catch (TaskCanceledException)
		{

		}

		return (Debugger.IsAttached && devices.Keys.Count == 0) ?
			[
			new Device("11:22:33:44:55:66", "192.168.1.2", "255.255.255.0", "DV300", "Fake System"),
			new Device("11:22:33:44:55:77", "192.168.1.3", "255.255.255.0", "DV350", "Fake System"),
			]
			: [.. devices.Keys];
	}

	public static async Task<MultilineCmd[]> LoadCommandsAsync()
	{
		var path = Path.Combine(Environment.CurrentDirectory, @"Commands/Commands.json");
		var json = await File.ReadAllTextAsync(path);
		JObject jsonObject = JObject.Parse(json);
		List<MultilineCmd> cmds = new();
		foreach (JProperty jp in jsonObject.Properties())
		{
			switch (jp.Value.Type)
			{
				case JTokenType.String:
					var sc = new MultilineCmd(jp.Name, [jp.Value.ToObject<string>() ?? ""]);
					cmds.Add(sc);
					break;
				case JTokenType.Array:
					var mc = new MultilineCmd(jp.Name, jp.Value.ToObject<string[]>() ?? []);
					cmds.Add(mc);
					break;
			}
		}

		return [.. cmds];

	}

	public static async Task<ScriptFile[]> LoadScriptsAsync()
	{
		ConcurrentBag<ScriptFile> scriptFiles = new();
		var scriptDir = Path.Combine(Environment.CurrentDirectory, "Scripts");
		var files = Directory.EnumerateFiles(scriptDir, "*.txt");
		await Parallel.ForEachAsync(files, async (file, token) =>
		{
			var lines = await File.ReadAllLinesAsync(file);
			lines = lines.Where(x => x is not ("" or ['#', ..])).ToArray();
			var s = new ScriptFile(Path.GetFileNameWithoutExtension(file), lines, file);
			scriptFiles.Add(s);
		});

		return [.. scriptFiles];
	}

	public static DeviceType PlatFormToDeviceType(this string platform)
	{
		var p = platform;

		if (p.Contains("300") || p.Contains("CV500"))
		{
			return DeviceType.Old;
		}
		else if (p.Contains("350") || p.Contains("400") || p.Contains("EV500") || p.Contains("DV500") || p.Contains("550"))
		{
			return DeviceType.New;
		}
		else
		{
			return DeviceType.Unknown;
		}
	}
}
